/**
 * 
 */
package util;

import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.UUID;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import jaxb.metagrammar.OperatorToUUIDStringAdapter;
import jaxb.util.SpecCollectionToArrayAdapter;
import metagrammar.Operator;
import parsetree.OperatorSpec;

/**
 * @author Joshua Lockerman
 *
 */
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class SpecWithSameOperatorCollection implements Collection<OperatorSpec>, HasId {
    
    public class ConstructionException extends Exception {
        
        private ConstructionException(Exception e)
        {
            super(e);
        }
    }
    
    private class SpecIterator implements Iterator<OperatorSpec> {
        
        Iterator<OperatorSpec> it;
        
        public SpecIterator(Iterator<OperatorSpec> it)
        {
            this.it = it;
        }
        
        @Override
        public boolean hasNext()
        {
            return it.hasNext();
        }
        
        @Override
        public OperatorSpec next()
        {
            return it.next();
        }
        
        @Override
        public void remove()
        {
            it.remove();
        }
        
    }
    
    private class SpecListIterator extends SpecIterator implements ListIterator<OperatorSpec> {
        
        public SpecListIterator(ListIterator<OperatorSpec> it)
        {
            super(it);
        }
        
        @Override
        public boolean hasPrevious()
        {
            return ((SpecListIterator) it).hasPrevious();
        }
        
        @Override
        public OperatorSpec previous()
        {
            return ((SpecListIterator) it).previous();
        }
        
        @Override
        public int nextIndex()
        {
            return ((SpecListIterator) it).nextIndex();
        }
        
        @Override
        public int previousIndex()
        {
            return ((SpecListIterator) it).previousIndex();
        }
        
        @Override
        public void set(OperatorSpec e)
        {
            if(e.getOperator().equals(operator)) {
                ((SpecListIterator) it).set(e);
            }
        }
        
        @Override
        public void add(OperatorSpec e)
        {
            if(e.getOperator().equals(operator)) {
                ((SpecListIterator) it).add(e);
            }
        }
        
    }
    
    @XmlJavaTypeAdapter(SpecCollectionToArrayAdapter.class)
    private final Collection<OperatorSpec> collection;
    
    @XmlJavaTypeAdapter(OperatorToUUIDStringAdapter.class)
    private Operator operator;
    
    private SpecWithSameOperatorCollection()
    {
        collection = null;
        operator = null;
    }
    
    public <C extends Collection<OperatorSpec>> SpecWithSameOperatorCollection(Operator operator,
            Class<C> type, Object... args) throws ConstructionException
            {
        Class<?>[] argTypes = new Class<?>[args.length];
        for(int i = 0; i < args.length; i++) {
            argTypes[i] = args[i].getClass();
        }
        try {
            collection = type.getConstructor(argTypes).newInstance(args);
            this.operator = operator;
        }
        catch(InstantiationException e) {
            throw new ConstructionException(e);
        }
        catch(IllegalAccessException e) {
            throw new ConstructionException(e);
        }
        catch(IllegalArgumentException e) {
            throw new ConstructionException(e);
        }
        catch(InvocationTargetException e) {
            throw new ConstructionException(e);
        }
        catch(NoSuchMethodException e) {
            throw new ConstructionException(e);
        }
        catch(SecurityException e) {
            throw new ConstructionException(e);
        }
            }
    
    @Override
    public int size()
    {
        return collection.size();
    }
    
    @Override
    public boolean isEmpty()
    {
        return collection.isEmpty();
    }
    
    @Override
    public boolean contains(Object o)
    {
        return collection.contains(o);
    }
    
    @Override
    public Iterator<OperatorSpec> iterator()
    {
        if(collection instanceof List) {
            return new SpecListIterator(((List<OperatorSpec>) collection).listIterator());
        }
        return new SpecIterator(collection.iterator());
    }
    
    @Override
    public Object[] toArray()
    {
        return collection.toArray();
    }
    
    @Override
    public <T> T[] toArray(T[] a)
    {
        return collection.toArray(a);
    }
    
    @Override
    public boolean add(OperatorSpec e)
    {
        if(e.getOperator().equals(operator)) {
            return collection.add(e);
        }
        return false;
    }
    
    @Override
    public boolean remove(Object o)
    {
        return collection.remove(o);
    }
    
    @Override
    public boolean containsAll(Collection<?> c)
    {
        return collection.contains(c);
    }
    
    @Override
    public boolean addAll(Collection<? extends OperatorSpec> c)
    {
        boolean added = false;
        for(OperatorSpec spec : c) {
            added |= add(spec);
        }
        return added;
    }
    
    @Override
    public boolean removeAll(Collection<?> c)
    {
        return collection.removeAll(c);
    }
    
    @Override
    public boolean retainAll(Collection<?> c)
    {
        return collection.retainAll(c);
    }
    
    @Override
    public void clear()
    {
        collection.clear();
    }
    
    public Operator getOperator()
    {
        return operator;
    }
    
    @Override
    public UUID getId()
    {
        return operator.getId();
    }
}
